home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 5 / Skunkware 5.iso / src / Tools / linuxdoc-sgml-1.1 / sgmls-1.1 / msgcat.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-05-03  |  16.1 KB  |  837 lines

  1. /* msgcat.c -
  2.    X/Open message catalogue functions and gencat utility.
  3.  
  4.      Written by James Clark (jjc@jclark.com).
  5. */
  6.  
  7. #include "config.h"
  8.  
  9. #ifndef HAVE_CAT
  10.  
  11. /* In this implementation the message catalogue format is the same as the
  12. message text source file format (see pp 42-43 of the X/Open
  13. Portability Guide, Issue 3, Volume 3.)  This means that you don't have
  14. to use the gencat utility, but it is still useful for checking and
  15. merging catalogues. */
  16.  
  17. /* Compile this with -DGENCAT to get the gencat utility. */
  18.  
  19. #include "std.h"
  20. #include "msgcat.h"
  21.  
  22. #ifdef USE_PROTOTYPES
  23. #define P(parms) parms
  24. #else
  25. #define P(parms) ()
  26. #endif
  27.  
  28. /* Default message set. */
  29. #define NL_SETD 1
  30.  
  31. #ifndef PATH_FILE_SEP
  32. #define PATH_FILE_SEP ':'
  33. #endif
  34.  
  35. #ifndef DEFAULT_NLSPATH
  36. #define DEFAULT_NLSPATH ""
  37. #endif
  38.  
  39. #ifndef DEFAULT_LANG
  40. #define DEFAULT_LANG "default"
  41. #endif
  42.  
  43. #define HASH_TAB_SIZE 251
  44.  
  45. struct message {
  46.      struct message *next;
  47.      unsigned msgnum;
  48.      unsigned setnum;
  49.      char *text;
  50. };
  51.      
  52. struct cat {
  53.      char *name;
  54.      int loaded;
  55.      int bad;
  56.      struct message *table[HASH_TAB_SIZE];
  57. };
  58.  
  59. static char *read_buf = 0;
  60. static unsigned read_buf_len = 0;
  61.  
  62. /* Errors that can be generated by read_catalog. */
  63.  
  64. enum cat_err {
  65.      E_ZERO,            /* not an error */
  66.      E_BADARG,
  67.      E_NOMEM,
  68.      E_NOSUCHCOMMAND,
  69.      E_INPUT,
  70.      E_EOF,
  71.      E_BADSEP,
  72.      E_BADLINE
  73. };
  74.  
  75. #ifdef GENCAT
  76. /* These must match enum cat_err. */
  77. static char *cat_errlist[] = {
  78.      "Error 0",
  79.      "Invalid argument to command",
  80.      "Out of memory",
  81.      "Unrecognized command",
  82.      "Input error",
  83.      "Unexpected end of file",
  84.      "Space or tab expected after message number",
  85.      "Invalid line",
  86. };
  87. #endif /* GENCAT */
  88.  
  89. #ifndef GENCAT
  90. /* The value of NLSPATH. */
  91. static char *nlspath = 0;
  92. /* The value of LANG. */
  93. static char *lang = 0;
  94. #endif /* not GENCAT */
  95.  
  96. static int current_lineno = -1;
  97. static enum cat_err cat_errno = E_ZERO;
  98.  
  99. #ifndef GENCAT
  100. static void load_catalog P((struct cat *));
  101. static FILE *find_catalog P((char *, char **));
  102. #endif
  103. static int read_catalog P((FILE *, struct message **));
  104. /* static void delete_set P((struct message **, unsigned));*/
  105. /* static void delete_message P((struct message **, unsigned, unsigned)); */
  106. static int hash P((unsigned setnum, unsigned msgnum));
  107. static char *parse_text P((FILE *, int));
  108.  
  109. #ifndef GENCAT
  110.  
  111. nl_catd catopen(name, oflag)
  112. char *name;
  113. int oflag;
  114. {
  115.      struct cat *catp;
  116.      int i;
  117.  
  118.      if (!name)
  119.       return 0;
  120.      
  121.      catp = (struct cat *)malloc(sizeof *catp);
  122.      if (!catp)
  123.       return 0;
  124.      for (i = 0; i < HASH_TAB_SIZE; i++)
  125.       catp->table[i] = 0;
  126.      catp->name = malloc(strlen(name) + 1);
  127.      catp->loaded = 0;
  128.      catp->bad = 0;
  129.      strcpy(catp->name, name);
  130.      return (nl_catd)catp;
  131. }
  132.  
  133. int catclose(catd)
  134. nl_catd catd;
  135. {
  136.      int i;
  137.      struct cat *catp = (struct cat *)catd;
  138.  
  139.      if (!catp)
  140.       return 0;
  141.  
  142.      for (i = 0; i < HASH_TAB_SIZE; i++) {
  143.       struct message *p, *nextp;
  144.       for (p = catp->table[i]; p; p = nextp) {
  145.            nextp = p->next;
  146.            free(p->text);
  147.            free((char *)p);
  148.       }
  149.      }
  150.      if (catp->name)
  151.       free(catp->name);
  152.      free((char *)catp);
  153.      return 0;
  154. }
  155.  
  156. char *catgets(catd, setnum, msgnum, dflt)
  157. nl_catd catd;
  158. int setnum, msgnum;
  159. char *dflt;
  160. {
  161.      struct message *p;
  162.      struct cat *catp;
  163.  
  164.      /* setnum and msgnum are required to be >= 1. */
  165.      if (!catd || setnum <= 0 || msgnum <= 0)
  166.       return dflt;
  167.      catp = (struct cat *)catd;
  168.      if (!catp->loaded)
  169.       load_catalog(catp);
  170.      if (catp->bad)
  171.       return dflt;
  172.      for (p = catp->table[hash(setnum, msgnum)]; p; p = p->next)
  173.       if (p->msgnum == msgnum && p->setnum == setnum)
  174.            break;
  175.      if (!p)
  176.       return dflt;
  177.      return p->text;
  178. }
  179.  
  180. static
  181. VOID load_catalog(catp)
  182. struct cat *catp;
  183. {
  184.      FILE *fp;
  185.      char *path;
  186.  
  187.      catp->loaded = 1;
  188.      fp = find_catalog(catp->name, &path);
  189.      if (!fp) {
  190.       catp->bad = 1;
  191.       return;
  192.      }
  193.      current_lineno = 0;
  194.      if (read_catalog(fp, catp->table) < 0)
  195.       catp->bad = 1;
  196.      fclose(fp);
  197.      if (read_buf) {
  198.       free(read_buf);
  199.       read_buf = 0;
  200.      }
  201.      read_buf_len = 0;
  202.      free(path);
  203. }
  204.  
  205. static
  206. FILE *find_catalog(name, pathp)
  207. char *name;
  208. char **pathp;
  209. {
  210.      char *path;
  211.  
  212.      if (!name)
  213.       return 0;
  214.      if (!nlspath) {
  215.       nlspath = getenv("NLSPATH");
  216.       if (!nlspath)
  217.            nlspath = DEFAULT_NLSPATH;
  218.      }
  219.      if (!lang) {
  220.       lang = getenv("LANG");
  221.       if (!lang)
  222.            lang = DEFAULT_LANG;
  223.      }
  224.      path = nlspath;
  225.      for (;;) {
  226.       char *p;
  227.       unsigned len = 0;
  228.  
  229.       for (p = path; *p != '\0' && *p != PATH_FILE_SEP; p++) {
  230.            if (*p == '%') {
  231.             if (p[1] == 'N') {
  232.              p++;
  233.              len += strlen(name);
  234.             }
  235.             else if (p[1] == 'L') {
  236.              p++;
  237.              len += strlen(lang);
  238.             }
  239.             else if (p[1] == '%') {
  240.              p++;
  241.              len++;
  242.             }
  243.             else
  244.              len++;
  245.  
  246.            }
  247.            else
  248.             len++;
  249.       }
  250.       if (len > 0) {
  251.            char *s, *try;
  252.            FILE *fp;
  253.            s = try = malloc(len + 1);
  254.            if (!s)
  255.             return 0;
  256.            for (p = path; *p != '\0' && *p != PATH_FILE_SEP; p++) {
  257.             if (*p == '%') {
  258.              if (p[1] == 'N') {
  259.                   p++;
  260.                   strcpy(s, name);
  261.                   s += strlen(name);
  262.              }
  263.              else if (p[1] == 'L') {
  264.                   p++;
  265.                   strcpy(s, lang);
  266.                   s += strlen(lang);
  267.              }
  268.              else if (p[1] == '%') {
  269.                   p++;
  270.                   *s++ = '%';
  271.              }
  272.              else
  273.                   *s++ = *p;
  274.             }
  275.             else
  276.              *s++ = *p;
  277.            }
  278.            *s++ = '\0';
  279.            fp = fopen(try, "r");
  280.            if (fp) {
  281.             *pathp = try;
  282.             return fp;
  283.            }
  284.            free(try);
  285.       }
  286.       if (*p == '\0')
  287.            break;
  288.       path = ++p;
  289.      }
  290.      return 0;
  291. }
  292.  
  293. #endif /* not GENCAT */
  294.  
  295. /* 0 success, -1 error */
  296.  
  297. static
  298. int parse_message(c, fp, table, setnum, quote)
  299. int c;
  300. FILE *fp;
  301. struct message **table;
  302. unsigned setnum;
  303. int quote;
  304. {
  305.      unsigned msgnum;
  306.      struct message *msgp;
  307.      char *text;
  308.      int hc;
  309.  
  310.      msgnum = c - '0';
  311.      for (;;) {
  312.       c = getc(fp);
  313.       if (!isdigit(c))
  314.            break;
  315.       msgnum = msgnum*10 + (c - '0');
  316.      }
  317.      if (c == '\n') {
  318.       delete_message(table, setnum, msgnum);
  319.       return 0;
  320.      }
  321.      if (c != ' ' && c != '\t') {
  322.       cat_errno = E_BADSEP;
  323.       return -1;
  324.      }
  325.      text = parse_text(fp, quote);
  326.      if (!text)
  327.       return -1;
  328.      hc = hash(setnum, msgnum);
  329.      for (msgp = table[hc]; msgp; msgp = msgp->next)
  330.       if (msgp->setnum == setnum && msgp->msgnum == msgnum)
  331.            break;
  332.      if (msgp)
  333.       free(msgp->text);
  334.      else {
  335.       msgp = (struct message *)malloc(sizeof *msgp);
  336.       if (!msgp) {
  337.            cat_errno = E_NOMEM;
  338.            return -1;
  339.       }
  340.       msgp->next = table[hc];
  341.       table[hc] = msgp;
  342.       msgp->msgnum = msgnum;
  343.       msgp->setnum = setnum;
  344.      }
  345.      msgp->text = text;
  346.      return 0;
  347. }
  348.  
  349. static
  350. char *parse_text(fp, quote)
  351. FILE *fp;
  352. int quote;
  353. {
  354.      unsigned i = 0;
  355.      char *p;
  356.      int c;
  357.      int quoted;
  358.  
  359.      c = getc(fp);
  360.      if (c == quote) {
  361.       quoted = 1;
  362.       c = getc(fp);
  363.      }
  364.      else
  365.       quoted = 0;
  366.      for (;; c = getc(fp)) {
  367.       if (c == EOF) {
  368.            if (ferror(fp)) {
  369.             cat_errno = E_INPUT;
  370.             return 0;
  371.            }
  372.            break;
  373.       }
  374.       if (c == '\n')
  375.            break;
  376.       /* XXX
  377.  
  378.          Can quotes be used in quoted message text if protected by \ ?
  379.  
  380.          Is it illegal to omit the closing quote if there's an opening
  381.          quote?
  382.  
  383.          Is it illegal to have anything after a closing quote?
  384.  
  385.       */
  386.  
  387.       if (quoted && c == quote) {
  388.            /* Skip the rest of the line. */
  389.            while ((c = getc(fp)) != '\n')
  390.             if (c == EOF) {
  391.              if (ferror(fp)) {
  392.                   cat_errno = E_INPUT;
  393.                   return 0;
  394.              }
  395.              break;
  396.             }
  397.            break;
  398.       }
  399.       if (c == '\\') {
  400.            int d;
  401.  
  402.            c = getc(fp);
  403.            if (c == EOF)
  404.             break;
  405.            switch (c) {
  406.            case '\n':
  407.             current_lineno++;
  408.             continue;
  409.            case 'n':
  410.             c = '\n';
  411.             break;
  412.            case 'b':
  413.             c = '\b';
  414.             break;
  415.            case 'f':
  416.             c = '\f';
  417.             break;
  418.            case 't':
  419.             c = '\t';
  420.             break;
  421.            case 'v':
  422.             c = '\v';
  423.             break;
  424.            case 'r':
  425.             c = '\r';
  426.             break;
  427.            case '\\':
  428.             c = '\\';
  429.             break;
  430.            case '0':
  431.            case '1':
  432.            case '2':
  433.            case '3':
  434.            case '4':
  435.            case '5':
  436.            case '6':
  437.            case '7':
  438.             c -= '0';
  439.             d = getc(fp);
  440.             if (d >= '0' && d <= '7') {
  441.              c = c*8 + d - '0';
  442.              d = getc(fp);
  443.              if (d >= '0' && d <= '7')
  444.                   c = c*8 + d - '0';
  445.              else if (d != EOF)
  446.                   ungetc(d,fp);
  447.             }
  448.             else if (d != EOF)
  449.              ungetc(d, fp);
  450.             if (c == '\0')
  451.              continue; /* XXX */
  452.             break;
  453.            default:
  454.             /* Ignore the quote. */
  455.             break;
  456.            }
  457.       }
  458.       if (i >= read_buf_len) {
  459.            if (!read_buf)
  460.             read_buf = malloc(read_buf_len = 40);
  461.            else
  462.             read_buf = realloc(read_buf, read_buf_len *= 2);
  463.            if (!read_buf) {
  464.             cat_errno = E_NOMEM;
  465.             return 0;
  466.            }
  467.       }
  468.       read_buf[i++] = c;
  469.      }
  470.      p = malloc(i + 1);
  471.      if (!p) {
  472.       cat_errno = E_NOMEM;
  473.       return 0;
  474.      }
  475.      memcpy(p, read_buf, i);
  476.      p[i] = '\0';
  477.      return p;
  478. }
  479.       
  480. /* 0 success, -1 error */
  481.  
  482. static
  483. int parse_command(fp, table, setnump, quotep)
  484. FILE *fp;
  485. struct message **table;
  486. unsigned *setnump;
  487. int *quotep;
  488. {
  489.      char buf[128];
  490.      if (fgets(buf, 128, fp) == NULL) {
  491.       cat_errno = ferror(fp) ? E_INPUT : E_EOF;
  492.       return -1;
  493.      }
  494.      if (buf[0] == ' ' || buf[0] == '\t' || buf[0] == '\n')
  495.       /* a comment */;
  496.      else if (strncmp(buf, "set", 3) == 0) {
  497.       if (sscanf(buf + 3, "%u", setnump) != 1) {
  498.            cat_errno = E_BADARG;
  499.            return -1;
  500.       }
  501.  
  502.      }
  503.      else if (strncmp(buf, "delset", 6) == 0) {
  504.       unsigned num;
  505.       if (sscanf(buf + 6, "%u", &num) != 1) {
  506.            cat_errno = E_BADARG;
  507.            return -1;
  508.       }
  509.       delete_set(table, num);
  510.       *setnump = NL_SETD;
  511.      }
  512.      else if (strncmp(buf, "quote", 5) == 0) {
  513.       char *p = buf + 5;
  514.       while (*p == ' ' || *p == '\t')
  515.            p++;
  516.       /* XXX should \ be allowed as the quote character? */
  517.       if (*p == '\0' || *p == '\n')
  518.            *quotep = -1;
  519.       else
  520.            *quotep = *p;
  521.      }
  522.      else {
  523.       cat_errno = E_NOSUCHCOMMAND;
  524.       return -1;
  525.      }
  526.      if (strchr(buf, '\n') == 0) {
  527.       int c;
  528.       while ((c = getc(fp)) != '\n' && c != EOF)
  529.            ;
  530.      }
  531.      return 0;
  532. }
  533.  
  534.  
  535. static
  536. VOID delete_set(table, setnum)
  537. struct message **table;
  538. unsigned setnum;
  539. {
  540.      int i;
  541.  
  542.      for (i = 0; i < HASH_TAB_SIZE; i++) {
  543.       struct message *p, *nextp;
  544.       for (p = table[i], table[i] = 0; p; p = nextp) {
  545.            nextp = p->next;
  546.            if (p->setnum == setnum)
  547.             free((char *)p);
  548.            else {
  549.             p->next = table[i];
  550.             table[i] = p;
  551.            }
  552.       }
  553.      }
  554. }
  555.  
  556.  
  557. static
  558. VOID delete_message(table, setnum, msgnum)
  559. struct message **table;
  560. unsigned setnum, msgnum;
  561. {
  562.      struct message **pp;
  563.      
  564.      for (pp = &table[hash(setnum, msgnum)]; *pp; pp = &(*pp)->next)
  565.       if ((*pp)->setnum == setnum && (*pp)->msgnum == msgnum) {
  566.            struct message *p = *pp;
  567.            *pp = p->next;
  568.            free(p->text);
  569.            free((char *)p);
  570.            break;
  571.       }
  572. }
  573.  
  574. /* 0 success, -1 error. On error cat_errno is set to the error number. */
  575.  
  576. static
  577. int read_catalog(fp, table)
  578. FILE *fp;
  579. struct message **table;
  580. {
  581.      int c;
  582.      unsigned setnum = NL_SETD;
  583.      int quote_char = -1;
  584.  
  585.      for (;;) {
  586.       /* start of line */
  587.       c = getc(fp);
  588.       if (c == EOF)
  589.            break;
  590.       ++current_lineno;
  591.       if (isdigit(c)) {
  592.            if (parse_message(c, fp, table, setnum, quote_char) < 0)
  593.             return -1;
  594.       }
  595.       else if (c == '$') {
  596.            if (parse_command(fp, table, &setnum, "e_char) < 0)
  597.             return -1;
  598.       }
  599.       else if (c != '\n') {
  600.            while ((c = getc(fp)) != '\n' && c != EOF)
  601.             if (c != ' ' && c != '\t') {
  602.              cat_errno = E_BADLINE;
  603.              return -1;
  604.             }
  605.            if (c == EOF)
  606.             break;
  607.       }
  608.      }
  609.      return 0;
  610. }
  611.  
  612. static
  613. int hash(setnum, msgnum)
  614. unsigned setnum, msgnum;
  615. {
  616.      return ((setnum << 8) + msgnum) % HASH_TAB_SIZE;
  617. }
  618.  
  619. #ifdef GENCAT
  620.  
  621. static char *program_name;
  622.  
  623. static int message_compare P((UNIV, UNIV));
  624. /* static void print_text P((char *, FILE *));*/
  625. /* static void usage P((void)); */
  626.  
  627. #ifdef VARARGS
  628. /* static void fatal(); */
  629. #else
  630. /* static void fatal P((char *,...)); */
  631. #endif
  632.  
  633. int main(argc, argv)
  634. int argc;
  635. char **argv;
  636. {
  637.      FILE *fp;
  638.      int i, j, nmessages;
  639.      struct message **list;
  640.      unsigned setnum;
  641.      struct message *table[HASH_TAB_SIZE];
  642.     
  643.      program_name = argv[0];
  644.      
  645.      if (argc < 3)
  646.       usage();
  647.  
  648.      for (i = 0; i < HASH_TAB_SIZE; i++)
  649.       table[i] = 0;
  650.      for (i = 1; i < argc; i++) {
  651.       errno = 0;
  652.       fp = fopen(argv[i], "r");
  653.       if (!fp) {
  654.            if (i > 1)
  655.             fatal("can't open `%s': %s", argv[i], strerror(errno));
  656.       }
  657.       else {
  658.            current_lineno = 0;
  659.            cat_errno = E_ZERO;
  660.            if (read_catalog(fp, table) < 0) {
  661.             assert(cat_errno != E_ZERO);
  662.             assert(cat_errno
  663.                < sizeof(cat_errlist)/sizeof(cat_errlist[0]));
  664.             fatal("%s:%d: %s", argv[i], current_lineno,
  665.               cat_errlist[cat_errno]);
  666.            }
  667.            fclose(fp);
  668.       }
  669.      }
  670.      
  671.      errno = 0;
  672.      fp = fopen(argv[1], "w");
  673.      if (!fp)
  674.       fatal("can't open `%s' for output: %s", argv[1], strerror(errno));
  675.      nmessages = 0;
  676.      for (i = 0; i < HASH_TAB_SIZE; i++) {
  677.       struct message *p;
  678.       for (p = table[i]; p; p = p->next)
  679.            nmessages++;
  680.      }
  681.      list = (struct message **)malloc(nmessages*sizeof(struct message *));
  682.      if (!list)
  683.       fatal("out of memory");
  684.      j = 0;
  685.      for (i = 0; i < HASH_TAB_SIZE; i++) {
  686.       struct message *p;
  687.       for (p = table[i]; p; p = p->next)
  688.            list[j++] = p;
  689.      }
  690.      assert(j == nmessages);
  691.      
  692.      qsort((UNIV)list, nmessages, sizeof(struct message *), message_compare);
  693.  
  694.      setnum = NL_SETD;
  695.      for (i = 0; i < nmessages; i++) {
  696.       struct message *p = list[i];
  697.       if (p->setnum != setnum) {
  698.            setnum = p->setnum;
  699.            fprintf(fp, "$set %u\n", setnum);
  700.       }
  701.       fprintf(fp, "%u ", p->msgnum);
  702.       print_text(p->text, fp);
  703.       putc('\n', fp);
  704.      }
  705.      if (fclose(fp) == EOF)
  706.       fatal("error closing `%s'", argv[1]);
  707.      return 0;
  708. }
  709.  
  710. static
  711. VOID usage()
  712. {
  713.      fprintf(stderr, "usage: %s catfile msgfile...\n", program_name);
  714.      exit(1);
  715. }
  716.  
  717. static
  718. #ifdef VARARGS
  719. VOID fatal(va_alist) va_dcl
  720. #else /* not VARARGS */
  721. VOID fatal(char *message,...)
  722. #endif /* not VARARGS */
  723. {
  724.      va_list ap;
  725.  
  726. #ifdef VARARGS
  727.      char *message;
  728.      va_start(ap);
  729.      message = va_arg(ap, char *);
  730. #else /* not VARARGS */
  731.      va_start(ap, message);
  732. #endif /* not VARARGS */ 
  733.      
  734.      fprintf(stderr, "%s: ", program_name);
  735.      vfprintf(stderr, message, ap);
  736.      putc('\n', stderr);
  737.      va_end(ap);
  738.      exit(1);
  739. }
  740.  
  741. static
  742. int message_compare(p1, p2)
  743. UNIV p1, p2;
  744. /* UNIV p1, UNIV p2; */
  745. {
  746.      struct message *m1 = *(struct message **)p1;
  747.      struct message *m2 = *(struct message **)p2;
  748.  
  749.      if (m1->setnum < m2->setnum)
  750.       return -1;
  751.      if (m1->setnum > m2->setnum)
  752.       return 1;
  753.      if (m1->msgnum < m2->msgnum)
  754.       return -1;
  755.      if (m1->msgnum > m2->msgnum)
  756.       return 1;
  757.      return 0;
  758. }
  759.  
  760. static
  761. VOID print_text(s, fp)
  762. char *s;
  763. FILE *fp;
  764. {
  765.      for (; *s; s++) {
  766.       if (*s == '\\')
  767.            fputs("\\\\", fp);
  768.       /* else if (ISASCII(*s) && isprint((UNCH)*s)) */
  769.       else if (ISASCII(*s) && isprint((char)*s))
  770.            putc(*s, fp);
  771.       else {
  772.            switch (*s) {
  773.            case '\n':
  774.             fputs("\\n", fp);
  775.             break;
  776.            case '\b':
  777.             fputs("\\b", fp);
  778.             break;
  779.            case '\f':
  780.             fputs("\\f", fp);
  781.             break;
  782.            case '\t':
  783.             fputs("\\t", fp);
  784.             break;
  785.            case '\v':
  786.             fputs("\\v", fp);
  787.             break;
  788.            case '\r':
  789.             fputs("\\r", fp);
  790.             break;
  791.            default:
  792.             fprintf(fp, "\\%03o", (unsigned char)*s);
  793.             break;
  794.            }
  795.       }
  796.      }
  797. }
  798.  
  799. #endif /* GENCAT */
  800.  
  801. #ifdef TEST
  802.  
  803. int main(argc, argv)
  804. int argc;
  805. char **argv;
  806. {
  807.      nl_catd catd;
  808.      int msgnum, setnum;
  809.      
  810.      if (argc != 2) {
  811.       fprintf(stderr, "usage: %s catalogue\n", argv[0]);
  812.       exit(1);
  813.      }
  814.      catd = catopen(argv[1], 0);
  815.      fprintf(stderr, "Enter set number, message number pairs:\n");
  816.      fflush(stderr);
  817.      while (scanf("%d %d", &setnum, &msgnum) == 2) {
  818.       char *msg = catgets(catd, setnum, msgnum, "<default>");
  819.       fprintf(stderr, "Returned \"%s\"\n", msg);
  820.       fflush(stderr);
  821.      }
  822.      return 0;
  823. }
  824.  
  825. #endif /* TEST */
  826.  
  827. #endif /* not HAVE_CAT */
  828. /*
  829. Local Variables:
  830. c-indent-level: 5
  831. c-continued-statement-offset: 5
  832. c-brace-offset: -5
  833. c-argdecl-indent: 0
  834. c-label-offset: -5
  835. End:
  836. */
  837.